#include "sdcard_apm32.hpp"
#include "apm32a10x_rcm.h"
#include "apm32a10x_gpio.h"
#include "apm32a10x_spi.h"
#include "system.h"

class SDCard_APM32_Callback : public SDCardCallback {
public:
    virtual uint8_t busInit(void);
    virtual uint8_t setSpeed(uint8_t speed);
    virtual uint8_t readWriteByte(uint8_t byte);
    virtual uint8_t setEnable(uint8_t status);
    uint8_t waitReady(void);
};

static SDCard_APM32 *sdcard_apm32 = nullptr;
static SDCard_APM32_Callback *sdcard_apm32_cb = nullptr;

static GPIO_T *sdcard_det_port = GPIOB;
static GPIO_PIN_T sdcard_det_pin = GPIO_PIN_11;

static GPIO_T *sdcard_cs_port = GPIOB;
static GPIO_PIN_T sdcard_cs_pin = GPIO_PIN_12;

static GPIO_T *sdcard_sck_port = GPIOB;
static GPIO_PIN_T sdcard_sck_pin = GPIO_PIN_13;

static GPIO_T *sdcard_miso_port = GPIOB;
static GPIO_PIN_T sdcard_miso_pin = GPIO_PIN_14;

static GPIO_T *sdcard_mosi_port = GPIOB;
static GPIO_PIN_T sdcard_mosi_pin = GPIO_PIN_15;

static SPI_T *sdcard_spi = SPI2;

SDCard_APM32::SDCard_APM32(SDCardCallback *callback,BusType bus) : SDCard(callback,bus) {
    GPIO_Config_T gpio_configuration;
    System_setPeripheralClockEnable(CLK_GPIOB,TRUE);
    gpio_configuration.pin = sdcard_det_pin;
    gpio_configuration.mode = GPIO_MODE_IN_FLOATING;
    gpio_configuration.speed = GPIO_SPEED_50MHz;
    GPIO_Config(sdcard_det_port,&gpio_configuration);
}

bool SDCard_APM32::checkCardExist(void){
    if ( GPIO_ReadInputBit(sdcard_det_port,sdcard_det_pin) == BIT_RESET ){
        System_delayMs(10);
        if ( GPIO_ReadInputBit(sdcard_det_port,sdcard_det_pin) == BIT_RESET ){
            return true;
        }
    }
    return false;
}

uint8_t SDCard_APM32_Callback::busInit(void){
    GPIO_Config_T gpio_configuration;
    SPI_Config_T spi_configuration;

    System_setPeripheralClockEnable(CLK_GPIOB,TRUE);
    System_setPeripheralClockEnable(CLK_SPI2,TRUE);

    GPIO_WriteBitValue(sdcard_cs_port,sdcard_cs_pin,BIT_SET);

    gpio_configuration.pin = sdcard_sck_pin;
    gpio_configuration.mode = GPIO_MODE_AF_PP;
    gpio_configuration.speed = GPIO_SPEED_50MHz;
    GPIO_Config(sdcard_sck_port,&gpio_configuration);
    gpio_configuration.pin = sdcard_mosi_pin;
    GPIO_Config(sdcard_mosi_port,&gpio_configuration);
    gpio_configuration.pin = sdcard_miso_pin;
    gpio_configuration.mode = GPIO_MODE_IN_FLOATING;
    GPIO_Config(sdcard_miso_port,&gpio_configuration);
    gpio_configuration.pin = sdcard_cs_pin;
    gpio_configuration.mode = GPIO_MODE_OUT_PP;
    GPIO_Config(sdcard_cs_port,&gpio_configuration);

    spi_configuration.baudrateDiv = SPI_BAUDRATE_DIV_256;
    spi_configuration.mode = SPI_MODE_MASTER;
    spi_configuration.direction = SPI_DIRECTION_2LINES_FULLDUPLEX;
    spi_configuration.length = SPI_DATA_LENGTH_8B;
    spi_configuration.polarity = SPI_CLKPOL_LOW;
    spi_configuration.phase = SPI_CLKPHA_1EDGE;
    spi_configuration.nss = SPI_NSS_SOFT;
    spi_configuration.firstBit = SPI_FIRSTBIT_MSB;
    spi_configuration.crcPolynomial = 7;
    SPI_Config(sdcard_spi,&spi_configuration);
    SPI_Enable(sdcard_spi);
    return 0;
}

uint8_t SDCard_APM32_Callback::setSpeed(uint8_t speed){
    if ( speed ){
        sdcard_spi->CTRL1 &= ~(0b111 << 3);
        sdcard_spi->CTRL1 |= SPI_BAUDRATE_DIV_4;
    }else{
        sdcard_spi->CTRL1 &= ~(0b111 << 3);
        sdcard_spi->CTRL1 |= SPI_BAUDRATE_DIV_256;
    }
    return speed;
}

uint8_t SDCard_APM32_Callback::readWriteByte(uint8_t byte){
    uint16_t retry = 0x0fff;
    sdcard_spi->DATA = byte;
    while ( SPI_I2S_ReadStatusFlag(sdcard_spi,SPI_FLAG_TXBE) != RESET && retry-- );
    retry = 0x0fff;
    while ( SPI_I2S_ReadStatusFlag(sdcard_spi,SPI_FLAG_RXBNE) == RESET && retry-- );
    return sdcard_spi->DATA;
}

uint8_t SDCard_APM32_Callback::setEnable(uint8_t status){
    if ( status ){
        GPIO_WriteBitValue(sdcard_cs_port,sdcard_cs_pin,BIT_RESET);
        if ( this->waitReady() == 0 ) return 1;
        GPIO_WriteBitValue(sdcard_cs_port,sdcard_cs_pin,BIT_SET);
        return 0;
    }else{
        GPIO_WriteBitValue(sdcard_cs_port,sdcard_cs_pin,BIT_SET);
    }
    return status;
}

uint8_t SDCard_APM32_Callback::waitReady(void){
    uint32_t t = 0;
    do {
        if ( this->readWriteByte(0xff) == 0xff ) return 0;
        t++;
    }while ( t < 0x00ffffff );
    return 1;
}

SDCard_APM32 *SDCard_APM32::getInstance(void){
    if ( sdcard_apm32 == nullptr ){
        sdcard_apm32_cb = new SDCard_APM32_Callback();
        sdcard_apm32_cb->busInit();
        sdcard_apm32 = new SDCard_APM32(sdcard_apm32_cb,SDCard::BusType::Bus_SPI);
    }
    return sdcard_apm32;
}